using System;
using data.contracts;
using NUnit.Framework;

namespace generator.tests
{
    [TestFixture]
    public class GeneratorTests
    {
        private Generator sut;
        private string result;
        private readonly string nl = Environment.NewLine;

        [SetUp]
        public void Setup() {
            sut = new Generator();
            sut.Result += x => result = x;
        }

        [Test]
        public void Two_empty_boards() {
            sut.Process(new[]{new Board("A", "a"), new Board("B", "b") });
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl +
                "namespace b {" + nl +
                "public interface IB {" + nl +
                "}" + nl +
                "public partial class B : IB {" + nl +
                "" + nl +
                "" + nl +
                "" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void A_board_with_a_single_wire() {
            var board = new Board("foo", "a");
            board.Add(new Wire("from.output", "int", "to.input") {
                FromPin = new Pin { Board = "from", Name = "output" },
                ToPin = new Pin { Board = "to", Name = "input" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IFrom {" + nl +
                "  event Action<int> output;" + nl + 
                "}" + nl +
                "public interface ITo {" + nl +
                "  void input(int message);" + nl +
                "}" + nl +
                "public interface IFoo {" + nl +
                "}" + nl +
                "public partial class foo : IFoo {" + nl +
                "" + nl +
                "" + nl +
                "  public foo(IFrom from, ITo to) {" + nl +
                "    from.output += to.input;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void A_board_with_a_single_wire_with_implicit_pin_names() {
            var board = new Board("foo", "a");
            board.Add(new Wire("from", "int", "to") {
                FromPin = new Pin { Board = "from" },
                ToPin = new Pin { Board = "to" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IFrom {" + nl +
                "  event Action<int> Result;" + nl + 
                "}" + nl +
                "public interface ITo {" + nl +
                "  void Process(int message);" + nl +
                "}" + nl +
                "public interface IFoo {" + nl +
                "}" + nl +
                "public partial class foo : IFoo {" + nl +
                "" + nl +
                "" + nl +
                "  public foo(IFrom from, ITo to) {" + nl +
                "    from.Result += to.Process;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void A_board_with_two_wires_with_implicit_pin_names_from_this_to_this() {
            var board = new Board("foo", "a");
            board.Add(new Wire("this", "int", "x") {
                FromPin = new Pin { Board = "this" },
                ToPin = new Pin { Board = "x" }
            });
            board.Add(new Wire("x", "int", "this") {
                FromPin = new Pin { Board = "x" },
                ToPin = new Pin { Board = "this" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IX {" + nl +
                "  event Action<int> Result;" + nl +
                "  void Process(int message);" + nl +
                "}" + nl +
                "public interface IFoo {" + nl +
                "  event Action<int> Result;" + nl +
                "  void Process(int message);" + nl +
                "}" + nl +
                "public partial class foo : IFoo {" + nl +
                "  public event Action<int> Result;" + nl +
                "" + nl +
                "  private Action<int> _Process;" + nl +
                "  public void Process(int message) {" + nl +
                "    _Process(message);" + nl +
                "  }" + nl +
                "  public foo(IX x) {" + nl +
                "    this._Process += x.Process;" + nl +
                "    x.Result += m => this.Result(m);" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void A_Board_with_two_wires_having_one_this_output() {
            var board = new Board("A", "a");
            board.Add(new Wire("producer.output", "int", "consumer.input") {
                FromPin = new Pin { Board = "producer", Name = "output" },
                ToPin = new Pin { Board = "consumer", Name = "input" }
            });
            board.Add(new Wire("consumer.output", "string", "this.output") {
                FromPin = new Pin { Board = "consumer", Name = "output" },
                ToPin = new Pin { Board = "this", Name = "output" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IProducer {" + nl +
                "  event Action<int> output;" + nl + 
                "}" + nl +
                "public interface IConsumer {" + nl +
                "  event Action<string> output;" + nl +
                "  void input(int message);" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "  event Action<string> output;" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "  public event Action<string> output;" + nl +
                "" + nl +
                "" + nl +
                "  public A(IProducer producer, IConsumer consumer) {" + nl +
                "    producer.output += consumer.input;" + nl +
                "    consumer.output += m => this.output(m);" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void Two_wires_between_the_same_pins() {
            var board = new Board("A", "a");
            board.Add(new Wire("producer.output", "int", "consumer.input") {
                FromPin = new Pin { Board = "producer", Name = "output" },
                ToPin = new Pin { Board = "consumer", Name = "input" }
            });
            board.Add(new Wire("producer.output", "int", "consumer.input") {
                FromPin = new Pin { Board = "producer", Name = "output" },
                ToPin = new Pin { Board = "consumer", Name = "input" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IProducer {" + nl +
                "  event Action<int> output;" + nl + 
                "}" + nl +
                "public interface IConsumer {" + nl +
                "  void input(int message);" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "  public A(IProducer producer, IConsumer consumer) {" + nl +
                "    producer.output += consumer.input;" + nl +
                "    producer.output += consumer.input;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void A_Board_having_a_wire_with_dots_in_from_an_to() {
            var board = new Board("A", "a");
            board.Add(new Wire("x.from", "int", "y.to") {
                FromPin = new Pin{Board="x", Name = "from"},
                ToPin=new Pin{Board="y", Name="to"}
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IX {" + nl +
                "  event Action<int> from;" + nl +
                "}" + nl +
                "public interface IY {" + nl +
                "  void to(int message);" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "  public A(IX x, IY y) {" + nl +
                "    x.from += y.to;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void A_board_with_one_wire_using_this_in_the_to_clause() {
            var board = new Board("A", "a");
            board.Add(new Wire("x.from", "int", "this.to") {
                FromPin = new Pin { Board = "x", Name = "from" },
                ToPin = new Pin { Board = "this", Name = "to" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IX {" + nl +
                "  event Action<int> from;" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "  event Action<int> to;" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "  public event Action<int> to;" + nl +
                "" + nl +
                "" + nl +
                "  public A(IX x) {" + nl +
                "    x.from += m => this.to(m);" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void One_Board_with_one_wire_having_one_this_input() {
            var board = new Board("A", "a");
            board.Add(new Wire("this.input", "int", "x.input") {
                FromPin = new Pin { Board = "this", Name = "input" },
                ToPin = new Pin { Board = "x", Name = "input" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IX {" + nl +
                "  void input(int message);" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "  void input(int message);" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "  private Action<int> _input;" + nl +
                "  public void input(int message) {" + nl +
                "    _input(message);" + nl +
                "  }" + nl +
                "  public A(IX x) {" + nl +
                "    this._input += x.input;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }


        [Test]
        public void One_Board_with_two_wires_having_one_this_input() {
            var board = new Board("A", "a");
            board.Add(new Wire("this.input", "int", "x.input1") {
                FromPin = new Pin { Board = "this", Name = "input" },
                ToPin = new Pin { Board = "x", Name = "input1" }
            });
            board.Add(new Wire("this.input", "int", "x.input2") {
                FromPin = new Pin { Board = "this", Name = "input" },
                ToPin = new Pin { Board = "x", Name = "input2" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IX {" + nl +
                "  void input1(int message);" + nl +
                "  void input2(int message);" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "  void input(int message);" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "  private Action<int> _input;" + nl +
                "  public void input(int message) {" + nl +
                "    _input(message);" + nl +
                "  }" + nl +
                "  public A(IX x) {" + nl +
                "    this._input += x.input1;" + nl +
                "    this._input += x.input2;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void Board_implementing_an_interface() {
            var board = new Board("A", "a");
            board.SetInterface("IFoo");

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public partial class A : IFoo {" + nl +
                "" + nl +
                "" + nl +
                "" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void Board_with_usings() {
            var board = new Board("A", "a");
            board.AddUsing("x.y.z");
            board.AddUsing("e.f.g");

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "using x.y.z;" + nl +
                "using e.f.g;" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void Board_with_external() {
            var board = new Board("A", "a");
            board.Add(new Wire("x.y", "", "x.z") {
                FromPin = new Pin { Board = "x", Name = "y" },
                ToPin = new Pin { Board = "x", Name = "z" }
            });
            board.AddExternal("IX");

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "  public A(IX x) {" + nl +
                "    x.y += x.z;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void Board_with_given_typename_in_from_attribute() {
            var board = new Board("A", "a");
            board.Add(new Wire("(Join<string,int,long>) join1.Output", "long", "bar.Input") {
                FromPin = new Pin { Board = "join1", Name = "Output", Type = "(Join<string,int,long>)" },
                ToPin = new Pin { Board = "bar", Name = "Input" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IBar {" + nl +
                "  void Input(long message);" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "  public A(Join<string,int,long> join1, IBar bar) {" + nl +
                "    join1.Output += bar.Input;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }

        [Test]
        public void Board_with_given_typename_in_to_attribute() {
            var board = new Board("A", "a");
            board.Add(new Wire("bar.Output", "string", "(Join<string,int,long>) join1.Input1") {
                FromPin = new Pin { Board = "bar", Name = "Output" },
                ToPin = new Pin { Board = "join1", Name = "Input1", Type = "(Join<string,int,long>)" }
            });

            sut.Process(new[]{board});
            
            Assert.That(result, Is.EqualTo(
                "using System;" + nl +
                "using System.Collections.Generic;" + nl +
                "using System.Linq;" + nl +
                "using System.Text;" + nl +
                "namespace a {" + nl +
                "public interface IBar {" + nl +
                "  event Action<string> Output;" + nl +
                "}" + nl +
                "public interface IA {" + nl +
                "}" + nl +
                "public partial class A : IA {" + nl +
                "" + nl +
                "" + nl +
                "  public A(IBar bar, Join<string,int,long> join1) {" + nl +
                "    bar.Output += join1.Input1;" + nl +
                "  }" + nl +
                "}" + nl +
                "" + nl +
                "}" + nl
              ));
        }
    }
}